﻿//*****************************************************************************
//    Description.....ESB Configuration helpers
//                                
//    Author..........Roman Kiss, rkiss@pathcom.com
//    Copyright © 2007 ATZ Consulting Inc. (see included license.rtf file)        
//                        
//    Date Created:    07/07/07
//
//    Date        Modified By     Description
//-----------------------------------------------------------------------------
//    07/07/07    Roman Kiss     Initial Revision
//    13/02/2015  Bram Veldhoen  After gladly adopting this awesome work by Roman Kiss,
//                               changed the namespace to match this project and started to make changes and additions.
//*****************************************************************************
//
#region Namespaces
using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Xsl;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Remoting.Messaging;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.ServiceModel.Web;
#endregion

namespace RockBus.ServiceModel
{
    #region ConfigHelper
    public class ConfigHelper
    {
        private ConfigHelper()
        {
        }

        /// <summary>
        /// Deserialize xml to the instance for a specific configuration section type
        /// </summary>
        /// <typeparam name="T">type of the section</typeparam>
        /// <param name="config">xml representat of the configuration section</param>
        /// <returns>Type of the configuration section</returns>
        public static T DeserializeSection<T>(string config) where T : class
        {
            T cfgSection = Activator.CreateInstance<T>();
            byte[] buffer = new ASCIIEncoding().GetBytes(config.TrimStart(new char[] { '\r', '\n', ' ' }));
            XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
            xmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment;

            using (MemoryStream ms = new MemoryStream(buffer))
            {
                using (XmlReader reader = XmlReader.Create(ms, xmlReaderSettings))
                {
                    try
                    {
                        Type cfgType = typeof(ConfigurationSection);
                        MethodInfo mi = cfgType.GetMethod("DeserializeSection", BindingFlags.Instance | BindingFlags.NonPublic);
                        mi.Invoke(cfgSection, new object[] { reader });
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(string.Format("DeserializeSection<{0}> failed, {1}", cfgSection.ToString(), ex.InnerException == null ? ex.Message : ex.InnerException.Message));
                    }
                }
            }
            return cfgSection;
        }

        public static KeyValueConfigurationCollection AppSettings(string config)
        {
            KeyValueConfigurationCollection col = new KeyValueConfigurationCollection();
            if (string.IsNullOrEmpty(config) == false)
            {
                AppSettingsSection appSettings = null;
                if (config.TrimStart().StartsWith("<"))
                {
                    appSettings = ConfigHelper.DeserializeSection<AppSettingsSectionHelper>(config).AppSettings;
                }
                else
                {
                    ExeConfigurationFileMap filemap = new ExeConfigurationFileMap();
                    filemap.ExeConfigFilename = config;
                    Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(filemap, ConfigurationUserLevel.None);
                    if (configuration != null)
                    {
                        appSettings = configuration.AppSettings;
                    }
                }
                if (appSettings != null && appSettings.Settings.Count > 0)
                {
                    return appSettings.Settings;
                }
            }
            return col;
        }
    }
    #endregion

    #region AppSettingsSectionHelper
    public sealed class AppSettingsSectionHelper : ConfigurationSection
    {
        private AppSettingsSection _appSettings = new AppSettingsSection();
        public AppSettingsSection AppSettings { get { return _appSettings; } }

        protected override void DeserializeSection(XmlReader reader)
        {
            Type cfgType = typeof(ConfigurationSection);
            MethodInfo mi = cfgType.GetMethod("DeserializeElement", BindingFlags.Instance | BindingFlags.NonPublic);

            reader.ReadStartElement("configuration");
            reader.ReadToFollowing("appSettings");
            reader.MoveToContent();
            mi.Invoke(_appSettings, new object[] { reader, false });
            reader.ReadEndElement();
            reader.ReadEndElement();
        }
    }
    #endregion

    #region ServiceModelConfigHelper
    public class ServiceModelConfigHelper
    {
        private ServiceModelConfigHelper()
        {
        }

        /// <summary>
        /// Get a specific ServiceElement from the config file
        /// </summary>
        /// <param name="name"></param>
        /// <param name="configFilename"></param>
        /// <returns></returns>
        /// <remarks>The default (process) config file is used when argument configFilename is empty/null.</remarks>
        public static ServiceElement GetServiceElement(string configurationName, string configFilename)
        {
            ExeConfigurationFileMap filemap = new ExeConfigurationFileMap();
            filemap.ExeConfigFilename = string.IsNullOrEmpty(configFilename) ? AppDomain.CurrentDomain.SetupInformation.ConfigurationFile : Path.GetFullPath(configFilename);
            Configuration config = ConfigurationManager.OpenMappedExeConfiguration(filemap, ConfigurationUserLevel.None);
            ServiceModelSectionGroup serviceModel = ServiceModelSectionGroup.GetSectionGroup(config);

            foreach (ServiceElement se in serviceModel.Services.Services)
            {
                if (se.Name == configurationName)
                {
                    return se;
                }
            }
            throw new ArgumentException(string.Format("The service '{0}' doesn't exist in the config {1}", configurationName, filemap.ExeConfigFilename));
        }

        public static ServiceElement GetServiceElement(string configurationName, Stream config)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// add service behaviors based on the config metadata
        /// </summary>
        /// <param name="host"></param>
        /// <param name="model"></param>
        public static void AddServiceBehaviors(ServiceHostBase host, ServiceModelSection model)
        {
            string serviceConfigurationName = host.Description.ConfigurationName;

            string behaviorConfigurationName = model.Services.Services[serviceConfigurationName].BehaviorConfiguration;
            if (!string.IsNullOrEmpty(behaviorConfigurationName))
            {
                MethodInfo mi = typeof(BehaviorExtensionElement).GetMethod("CreateBehavior", BindingFlags.Instance | BindingFlags.NonPublic);

                if (model.Behaviors.ServiceBehaviors.ContainsKey(behaviorConfigurationName) == false)
                    throw new Exception(string.Format("Missing service behavior '{0}' for service '{1}'", behaviorConfigurationName, serviceConfigurationName));

                foreach (ServiceBehaviorElement sbe in model.Behaviors.ServiceBehaviors)
                {
                    if (sbe.Name == behaviorConfigurationName)
                    {
                        foreach (BehaviorExtensionElement bee in sbe)
                        {
                            IServiceBehavior behavior = mi.Invoke(bee, null) as IServiceBehavior;
                            host.Description.Behaviors.Add(behavior);
                        }
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// add service's endpoint behaviors based on the config metadata
        /// </summary>
        /// <param name="host"></param>
        /// <param name="model"></param>
        public static void AddEndpointBehaviors(ServiceHostBase host, ServiceModelSection model)
        {
            string serviceConfigurationName = host.Description.ConfigurationName;

            MethodInfo mi = typeof(BehaviorExtensionElement).GetMethod("CreateBehavior", BindingFlags.Instance | BindingFlags.NonPublic);

            // walk through all endpoints for this service
            foreach (ServiceEndpointElement see in model.Services.Services[serviceConfigurationName].Endpoints)
            {
                if (string.IsNullOrEmpty(see.BehaviorConfiguration)) continue;

                if (model.Behaviors.EndpointBehaviors.ContainsKey(see.BehaviorConfiguration) == false)
                    throw new Exception(string.Format("Missing endpoint behavior '{0}' for service '{1}'", see.BehaviorConfiguration, serviceConfigurationName));

                foreach (EndpointBehaviorElement ebe in model.Behaviors.EndpointBehaviors)
                {
                    if (ebe.Name == see.BehaviorConfiguration)
                    {
                        foreach (string key in ebe.ElementInformation.Properties.Keys)
                        {
                            if (key == "name") continue;
                            IEndpointBehavior behavior = mi.Invoke(ebe.ElementInformation.Properties[key].Value, null) as IEndpointBehavior;
                            host.Description.Endpoints.Find(see.Address).Behaviors.Add(behavior);
                        }
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// add binding for all service endpoints based on the config metadata
        /// </summary>
        /// <param name="host"></param>
        /// <param name="model"></param>
        public static void AddServiceEndpointBinding(ServiceHostBase host, ServiceModelSection model)
        {
            string serviceConfigurationName = host.Description.ConfigurationName;

            ServiceEndpointElementCollection endpoints = model.Services.Services[serviceConfigurationName].Endpoints;
            for (int ii = 0; ii < endpoints.Count; ii++)
            {
                if (host.Description.Endpoints[ii].Binding == null)
                {
                    string bindingConfigurationName = endpoints[ii].BindingConfiguration;
                    Binding binding = CreateEndpointBinding(bindingConfigurationName, model);
                    if (binding == null)
                        throw new Exception(string.Format("Missing endpoint binding '{0}' in the service '{1}'", bindingConfigurationName, serviceConfigurationName));
                    host.Description.Endpoints[ii].Binding = binding;
                }
            }
        }

        /// <summary>
        /// Add client's endpoint behaviors
        /// </summary>
        /// <param name="behaviorConfiguration"></param>
        /// <param name="endpoint"></param>
        /// <param name="model"></param>
        public static void AddChannelEndpointBehaviors(string behaviorConfiguration, ServiceEndpoint endpoint, ServiceModelSection model)
        {
            if (endpoint == null)
                throw new ArgumentNullException("endpoint");

            if (string.IsNullOrEmpty(behaviorConfiguration))
                return;

            if (model.Behaviors.EndpointBehaviors.ContainsKey(behaviorConfiguration) == false)
                throw new Exception(string.Format("Missing endpoint behavior '{0}' for endpoint name '{1}'", behaviorConfiguration, endpoint.Name));

            MethodInfo mi = typeof(BehaviorExtensionElement).GetMethod("CreateBehavior", BindingFlags.Instance | BindingFlags.NonPublic);
            foreach (EndpointBehaviorElement ebe in model.Behaviors.EndpointBehaviors)
            {
                if (ebe.Name == behaviorConfiguration)
                {
                    foreach (string key in ebe.ElementInformation.Properties.Keys)
                    {
                        if (key == "name") continue;
                        IEndpointBehavior behavior = mi.Invoke(ebe.ElementInformation.Properties[key].Value, null) as IEndpointBehavior;
                        endpoint.Behaviors.Add(behavior);
                    }
                    break;
                }
            }
        }

        /// <summary>
        /// create endpoint binding
        /// </summary>
        /// <param name="bindingConfigurationName"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        public static Binding CreateEndpointBinding(string bindingConfigurationName, ServiceModelSection model)
        {
            Binding binding = null;

            BindingCollectionElement bce = model.Bindings.BindingCollections.Find(delegate(BindingCollectionElement element)
            {
                if (element.ConfiguredBindings.Count == 0) return false;
                for (int ii = 0; ii < element.ConfiguredBindings.Count; ii++)
                {
                    if (element.ConfiguredBindings[ii].Name == bindingConfigurationName) return true;
                }
                return false;
            });

            if (bce == null)
                return binding;

            foreach (IBindingConfigurationElement configuredBinding in bce.ConfiguredBindings)
            {
                if (bce.BindingType == typeof(CustomBinding))
                {
                    MethodInfo mi = typeof(BindingElementExtensionElement).GetMethod("CreateBindingElement", BindingFlags.Instance | BindingFlags.NonPublic);
                    foreach (CustomBindingElement cbe in model.Bindings.CustomBinding.Bindings)
                    {
                        if (bindingConfigurationName == cbe.Name)
                        {
                            CustomBinding cb = new CustomBinding();
                            foreach (BindingElementExtensionElement element in cbe)
                            {
                                cb.Elements.Add((BindingElement)mi.Invoke(element, null));
                            }
                            return cb;
                        }
                    }
                    break;
                }
                else if (bce.BindingType == typeof(NetNamedPipeBinding))
                    binding = new NetNamedPipeBinding();
                else if (bce.BindingType == typeof(NetTcpBinding))
                    binding = new NetTcpBinding();
                else if (bce.BindingType == typeof(NetTcpContextBinding))
                    binding = new NetTcpContextBinding();
                else if (bce.BindingType == typeof(NetPeerTcpBinding))
                    binding = new NetPeerTcpBinding();
                else if (bce.BindingType == typeof(NetMsmqBinding))
                    binding = new NetMsmqBinding();
                else if (bce.BindingType == typeof(WSHttpBinding))
                    binding = new WSHttpBinding();
                else if (bce.BindingType == typeof(WSHttpContextBinding))
                    binding = new WSHttpContextBinding();
                else if (bce.BindingType == typeof(BasicHttpBinding))
                    binding = new BasicHttpBinding();
                else if (bce.BindingType == typeof(BasicHttpContextBinding))
                    binding = new BasicHttpContextBinding();
                else if (bce.BindingType == typeof(WebHttpBinding))
                    binding = new WebHttpBinding();
                else if (bce.BindingType == typeof(WSDualHttpBinding))
                    binding = new WSDualHttpBinding();
                else if (bce.BindingType == typeof(WSFederationHttpBinding))
                    binding = new WSFederationHttpBinding();
                else if (bce.BindingType == typeof(WS2007FederationHttpBinding))
                    binding = new WS2007FederationHttpBinding();
                else if (bce.BindingType == typeof(WS2007HttpBinding))
                    binding = new WS2007HttpBinding();
                else
                    break;

                configuredBinding.ApplyConfiguration(binding);
                break;
            }
            return binding;
        }

        /// <summary>
        /// Create list of all client's endpoints
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public static List<ServiceEndpoint> ListOfClientEndpoints(ServiceModelSection model)
        {
            List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
            if (model.Client != null && model.Client.Endpoints != null && model.Client.Endpoints.Count > 0)
            {
                foreach (ChannelEndpointElement element in model.Client.Endpoints)
                {
                    // find a contract type
                    Type contractType = Type.GetType(element.Contract, false);
                    if (contractType == null)
                    {
                        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                        {
                            contractType = assembly.GetType(element.Contract, false);
                            if (contractType != null) 
                                break;
                        }
                    }
                    if(contractType == null)
                        throw new Exception(string.Format("Contract type '{0}' failed in the endpoint name '{1}'", element.Contract, element.Name));

                    ServiceEndpoint endpoint = new ServiceEndpoint(ContractDescription.GetContract(contractType));
                    endpoint.Address = new EndpointAddress(element.Address);
                    endpoint.Name = element.Name;
                    if (endpoint.Binding == null)
                    {
                        endpoint.Binding = CreateEndpointBinding(element.BindingConfiguration, model);
                        if (endpoint.Binding == null)
                            throw new Exception(string.Format("Missing endpoint binding '{0}' in the endpoint name '{1}'", element.BindingConfiguration, element.Name));
                    }
                    ServiceModelConfigHelper.AddChannelEndpointBehaviors(element.BehaviorConfiguration, endpoint, model);
                    endpoints.Add(endpoint);
                }
            }
            return endpoints;
        }
    }
    #endregion

    #region ServiceModelSection
    public sealed class ServiceModelSection : ConfigurationSection
    {
        public ServicesSection Services { get; set; }
        public ClientSection Client { get; set; }
        public BehaviorsSection Behaviors { get; set; }
        public BindingsSection Bindings { get; set; }
        public ExtensionsSection Extensions { get; set; }
        public DiagnosticSection Diagnostics { get; set; }

        protected override void DeserializeSection(XmlReader reader)
        {
            Type cfgType = typeof(ConfigurationSection);
            MethodInfo mi = cfgType.GetMethod("DeserializeElement", BindingFlags.Instance | BindingFlags.NonPublic);

            reader.ReadStartElement("configuration");
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                if (reader.Name == "system.serviceModel")
                {
                    reader.ReadStartElement();
                    while (reader.NodeType != XmlNodeType.EndElement)
                    {
                        #region sections
                        string element = reader.Name;
                        try
                        {
                            if (reader.IsEmptyElement || reader.NodeType == XmlNodeType.Whitespace)
                            {
                                reader.Skip();
                                continue;
                            }

                            if (reader.Name == "diagnostics")
                            {
                                Diagnostics = new DiagnosticSection();
                                mi.Invoke(Diagnostics, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                            else if (reader.Name == "extensions")
                            {
                                Extensions = new ExtensionsSection();
                                mi.Invoke(Extensions, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                            else if (reader.Name == "services")
                            {
                                Services = new ServicesSection();
                                mi.Invoke(Services, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                            else if (reader.Name == "bindings")
                            {
                                Bindings = new BindingsSection();
                                /////
                                //if (Extensions != null && Extensions.BindingElementExtensions != null && Extensions.BindingElementExtensions.Count > 0)
                                //{
                                //    PropertyInfo fi = typeof(BindingsSection).GetProperty("Properties", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic );
                                //    ConfigurationPropertyCollection properties = (ConfigurationPropertyCollection)fi.GetValue(Bindings, null);
                                //    if (properties != null)
                                //    {
                                //        foreach (ExtensionElement extensions in Extensions.BindingElementExtensions)
                                //        {
                                //            if (extensions != null)
                                //            {
                                //                ConfigurationProperty property = new ConfigurationProperty(extensions.Name, Type.GetType(extensions.Type, true), null, ConfigurationPropertyOptions.None);
                                //                properties.Add(property);
                                //            }
                                //        }
                                //    }
                                //}
                                /////
                                mi.Invoke(Bindings, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                            else if (reader.Name == "behaviors")
                            {
                                Behaviors = new BehaviorsSection();
                                mi.Invoke(Behaviors, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                            else if (reader.Name == "client")
                            {
                                Client = new ClientSection();
                                mi.Invoke(Client, new object[] { reader, false });
                                reader.ReadEndElement();
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new Exception(string.Format("Deserializing '{0}' failed, {1}", element, ex.InnerException == null ? ex.Message : ex.InnerException.Message));
                        }
                        #endregion
                        reader.MoveToContent();
                    }
                }
                reader.Skip();
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }
    }
    #endregion

    #region LocalRepositorySection
    public class LocalRepositorySection : ConfigurationSection
    {
        [ConfigurationProperty("enable", DefaultValue = "true", IsRequired = false)]
        public bool Enable
        {
            get { return (bool)this["enable"]; }
            set { this["enable"] = value; }
        }
        [ConfigurationProperty("endpointName", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\", MaxLength = 128)]
        public string EndpointName
        {
            get { return (string)this["endpointName"]; }
            set { this["endpointName"] = value; }
        }

        [ConfigurationProperty("HostMetadata", IsRequired = true)]
        public HostMetadataElement HostMetadata
        {
            get { return (HostMetadataElement)this["HostMetadata"]; }
            set { this["HostMetadata"] = value; }
        }
    }

    public class HostMetadataElement : ConfigurationElement
    {
        public string MachineName { get { return Environment.MachineName; } }
        public string ApplicationName { get { return AppDomain.CurrentDomain.FriendlyName; } }

        [ConfigurationProperty("hostName", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\", MaxLength = 128)]
        public string HostName
        {
            get { return (string)this["hostName"]; }
            set { this["hostName"] = value; }
        }

        [ConfigurationProperty("assemblyNames", IsRequired = false)]
        public string AssemblyNames
        {
            get { return (string)this["assemblyNames"]; }
            set { this["assemblyNames"] = value; }
        }

        [ConfigurationProperty("Services", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(ServiceMetadaCollection))]
        public ServiceMetadaCollection Services
        {
            get { return this["Services"] as ServiceMetadaCollection; }
        }
    }

    public class ServiceMetadaCollection : AnyElementCollection<ServiceMetadataElement>
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ServiceMetadataElement)element).Name;
        }
    }
    public class ServiceMetadataElement : ConfigurationElement
    {
        public string Config { get; set; }

        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\", MaxLength = 128)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("topic", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&()[]{};'\"|", MaxLength = 256)]
        public string Topic
        {
            get { return (string)this["topic"]; }
            set { this["topic"] = value; }
        }

        [ConfigurationProperty("appDomainHostName", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&()[]{}/'\"|\\", MaxLength = 128)]
        public string AppDomainHostName
        {
            get { return (string)this["appDomainHostName"]; }
            set { this["appDomainHostName"] = value; }
        }

        [ConfigurationProperty("baseAddresses", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#%^&*()[]{}'\"|", MaxLength = 512)]
        public string BaseAddresses
        {
            get { return (string)this["baseAddresses"]; }
            set { this["baseAddresses"] = value; }
        }

        [ConfigurationProperty("xoml", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{};'\"|", MaxLength = 512)]
        public string Xoml
        {
            get { return (string)this["xoml"]; }
            set { this["xoml"] = value; }
        }

        [ConfigurationProperty("rules", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{};'\"|", MaxLength = 512)]
        public string Rules
        {
            get { return (string)this["rules"]; }
            set { this["rules"] = value; }
        }

        [ConfigurationProperty("wsdl", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{};'\"|", MaxLength = 512)]
        public string Wsdl
        {
            get { return (string)this["wsdl"]; }
            set { this["wsdl"] = value; }
        }

        [ConfigurationProperty("xslt", IsRequired = false)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{};'\"|", MaxLength = 512)]
        public string Xslt
        {
            get { return (string)this["xslt"]; }
            set { this["xslt"] = value; }
        }

        protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
        {
            // workaround for restricted name of the property
            if (name == "config")
            {
                Config = value;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    #endregion

    #region Mediators
    public enum MediatorMode
    {
        None,
        Input,
        Output,
        Error,
        Custom
    }

    [Serializable]
    public class ServiceMediatorsSection : ConfigurationSection
    {
        [ConfigurationProperty("operations", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(OperationElementCollection), AddItemName = "operation")]
        public OperationElementCollection Operations
        {
            get { return this["operations"] as OperationElementCollection; }
        }

        public ServiceMediatorsSection()
            : base()
        {
        }
        public ServiceMediatorsSection(string section)
        {
            if (string.IsNullOrEmpty(section) == false)
            {
                byte[] buffer = new ASCIIEncoding().GetBytes(section.TrimStart(new char[] { '\r', '\n', ' ' }));
                XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
                xmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment;

                using (MemoryStream ms = new MemoryStream(buffer))
                {
                    using (XmlReader reader = XmlReader.Create(ms, xmlReaderSettings))
                    {
                        base.DeserializeSection(reader);
                    }
                }
            }
        }

        public static OperationElement GetMediators(string section, string action)
        {
            ServiceMediatorsSection serviceMediators = new ServiceMediatorsSection(section);
            return serviceMediators.Operations.Get(action);
        }
        public static MediatorElementCollection GetInputMediators(string section, string action)
        {
            ServiceMediatorsSection serviceMediators = new ServiceMediatorsSection(section);
            OperationElement operation = serviceMediators.Operations.Get(action);
            return operation == null ? null : operation.InputMediators;
        }
        public static MediatorElementCollection GetOutputMediators(string section, string action)
        {
            ServiceMediatorsSection serviceMediators = new ServiceMediatorsSection(section);
            OperationElement operation = serviceMediators.Operations.Get(action);
            return operation == null ? null : operation.OutputMediators;
        }
        public static MediatorElementCollection GetErrorMediators(string section, string action)
        {
            ServiceMediatorsSection serviceMediators = new ServiceMediatorsSection(section);
            OperationElement operation = serviceMediators.Operations.Get(action);
            return operation == null ? null : operation.ErrorMediators;
        }
        public static MediatorElementCollection GetMediators(string section, string action, MediatorMode mode)
        {
            // validate parameters
            if (string.IsNullOrEmpty(section) || string.IsNullOrEmpty(action) || mode == MediatorMode.None)
            {
                return null;
            }

            ServiceMediatorsSection serviceMediators = new ServiceMediatorsSection(section);
            OperationElement operation = serviceMediators.Operations.Get(action);
            if (operation != null)
            {
                if (mode == MediatorMode.Input)
                    return operation.InputMediators;
                else if (mode == MediatorMode.Output)
                    return operation.OutputMediators;
                else if (mode == MediatorMode.Error)
                    return operation.ErrorMediators;
                else
                    return null;
            }
            else
            {
                return null;
            }
        }
    }

    public class OperationElementCollection : AnyElementCollection<OperationElement>
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((OperationElement)element).Action;
        }
    }
    public class OperationElement : ConfigurationElement
    {
        [ConfigurationProperty("action", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}\"|", MaxLength = 128)]
        public string Action
        {
            get { return (string)this["action"]; }
            set { this["action"] = value; }
        }

        [ConfigurationProperty("input", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(MediatorElementCollection), AddItemName = "mediator")]
        public MediatorElementCollection InputMediators
        {
            get { return this["input"] as MediatorElementCollection; }
        }

        [ConfigurationProperty("output", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(MediatorElementCollection), AddItemName = "mediator")]
        public MediatorElementCollection OutputMediators
        {
            get { return this["output"] as MediatorElementCollection; }
        }

        [ConfigurationProperty("error", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(MediatorElementCollection), AddItemName = "mediator")]
        public MediatorElementCollection ErrorMediators
        {
            get { return this["error"] as MediatorElementCollection; }
        }
        protected override void PostDeserialize()
        {
            base.PostDeserialize();
        }
    }

    public class MediatorElementCollection : AnyElementCollection<MediatorElement>
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((MediatorElement)element).Name;
        }
    }
    public class MediatorElement : ConfigurationElement
    {
        XsltArgumentList list = new XsltArgumentList();

        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\", MaxLength = 128)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("xpath", IsRequired = false)]
        public string Xpath
        {
            get { return (string)this["xpath"]; }
            set { this["xpath"] = value; }
        }

        [ConfigurationProperty("validation", IsRequired = false)]
        public bool Validation
        {
            get { return (bool)this["validation"]; }
            set { this["validation"] = value; }
        }

        [ConfigurationProperty("params", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(ParamElementCollection), AddItemName = "param")]
        public ParamElementCollection Params
        {
            get { return this["params"] as ParamElementCollection; }
        }

        [ConfigurationProperty("functions", IsRequired = false, IsDefaultCollection = false), ConfigurationCollection(typeof(FunctionElementCollection), AddItemName = "function")]
        public FunctionElementCollection Functions
        {
            get { return this["functions"] as FunctionElementCollection; }
        }

        public string Xslt { get; set; }

        protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
        {
            if (name == "xslt")
            {
                Xslt = value;
                return true;
            }
            return false;
        }
        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
        {
            if (reader.Name == "xslt" && string.IsNullOrEmpty(Xslt))
            {
                reader.ReadStartElement();
                Xslt = reader.ReadOuterXml();
                return true;
            }
            return false;
        }

        protected override void PostDeserialize()
        {
            if (string.IsNullOrEmpty(Xslt))
                throw new ConfigurationErrorsException(string.Format("The xslt is required property of the mediator, name={0}", Name));
            else
                base.PostDeserialize();

            try
            {
                if (Params != null)
                {
                    for (int ii = 0; ii < Params.Count; ii++)
                    {
                        list.AddParam(Params[ii].Name, Params[ii].Namespace, Params[ii].Value);
                    }
                }
                if (Functions != null)
                {
                    for (int ii = 0; ii < Functions.Count; ii++)
                    {
                        Type type = Type.GetType(Functions[ii].Type);
                        if (type == null)
                            throw new NullReferenceException(string.Format("Can't create a type from value '{0}'", Functions[ii].Type));
                        object obj = Activator.CreateInstance(type);
                        if (obj == null)
                            throw new NullReferenceException(string.Format("Can't create an instance of the '{0}'", Functions[ii].Type));
                        list.AddExtensionObject(Functions[ii].Name, obj);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(string.Format("The mediator '{0}' failed in the params or functions elements", this.Name), ex);
            }
        }

        public XsltArgumentList XsltArgumentList
        {
            get { return list; }
        }
    }

    public class ParamElementCollection : AnyElementCollection<ParamElement>
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ParamElement)element).Name;
        }
    }
    public class ParamElement : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}\"|", MaxLength = 128)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("namespace", IsRequired = false, DefaultValue = null)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}\"|", MaxLength = 128)]
        public string Namespace
        {
            get { return (string)this["namespace"]; }
            set { this["namespace"] = value; }
        }

        [ConfigurationProperty("value", IsRequired = true)]
        public string Value
        {
            get { return (string)this["value"]; }
            set { this["value"] = value; }
        }
    }

    public class FunctionElementCollection : AnyElementCollection<FunctionElement>
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((FunctionElement)element).Name;
        }
    }
    public class FunctionElement : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}\"|", MaxLength = 128)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("type", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}\"|", MaxLength = 526)]
        public string Type
        {
            get { return (string)this["type"]; }
            set { this["type"] = value; }
        }
    }

    public abstract class AnyElementCollection<T> : ConfigurationElementCollection where T : class
    {
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
        }

        public T this[int index]
        {
            get { return base.BaseGet(index) as T; }
            set
            {
                if (base.BaseGet(index) != null)
                {
                    base.BaseRemoveAt(index);
                }
                base.BaseAdd(index, value as ConfigurationElement);
            }
        }

        public void Add(T element)
        {
            base.BaseAdd(element as ConfigurationElement);
        }

        public void Clear()
        {
            base.BaseClear();
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return Activator.CreateInstance<T>() as ConfigurationElement;
        }

        public T Get(string key)
        {
            return base.BaseGet(key) as T;
        }

        public virtual void Remove(T element)
        {
            string name = (string)GetElementKey(element as ConfigurationElement);
            base.BaseRemove(name);
        }

        public virtual void Remove(string name)
        {
            base.BaseRemove(name);
        }

        public virtual void RemoveAt(int index)
        {
            base.BaseRemoveAt(index);
        }
    }

    #endregion
}

